//
//  main.c
//  Pointer
//
//  Created by admin on 2019/7/22.
//  Copyright © 2019 猫爷爷的酱醋茶. All rights reserved.
//

/*
 
 指针
 
 1）指针的声明：
    a）数据类型* 指针变量名称
        int* p1;   int* 读作int指针  p1指针变量只能用来存储变量地址。
        只能存储和这个指针相同的数据类型的 普通变量的地址
    b）指针变量也是一个变量，所以可以批量声明指针
        注意：int* p1,p2,p3;
            这样声明的话，只有p1是int指针，p2和p3是int类型。如果希望全是指针，应该这么声明：
            int *p1,*p2,*p3;
 
 2）使用指针 间接的操作指针指向的变量 取值/赋值
    格式： *指针变量，代表这个指针指向的变量；
     int num = 10;
     int* p1 = &num;
     *p1 完全等价于 num;
     即*p1 = 20, 即赋值20给num，值改为20.
 
 3）野指针
    我们声明一个指针变量，如果没有为其初始化，那么这个指针变量中是有值的，垃圾值，可能指向一块随机的空间
    如果没有变量的地址初始化给指针，一般情况下，给指针初始化NULL，表示该指针不指向任何地址。
    如果一个指针变量的值是NULL，这个时候通过指针变量去访问指向的变量的时候，一定会报错。
 
 4）指针的应用：
    a）当函数的参数的类型是int、char、double、float的时候，
       这个时候参数传递的是值传递，在函数的内部去修改形参的值，实参不受影响；
    b）当函数的参数的类型是数组的时候，这个时候参数传递的是地址传递，
       在函数内部修改参数数组元素的时候，其实修改的就是实参数组的元素。
    c）指针作为函数参数，可以在函数内部修改实参变量的值。
    如果函数需要返回多个数据，使用指针作为函数参数，让调用者将自己的变量的地址传递到函数的内部，函数的内部通过指针就可以修改参数变量的值。
            第一种方法 例：TheMaxNumberAndMinNumber();  实现：ReturnMultipeData();
            第二种方法 例：MaximumAndMinimum(); 实现：ReturnTheDate();
    d）指针作为函数的返回值
        注意： 函数可以返回局部变量的值，但是不能返回局部变量的地址
        a.不能返回定义在局部变量的地址，因为函数内的变量，在函数执行完毕后就立即被系统回收
            例：PointerLocalVariable(),实现：Achieve()
        b.可以将函数内的变量申请在堆区，在堆区的变量，如果不用free函数回收，只会在程序结束后回收，所以可以返回堆区地址
            例：ReturnStackVariable(),实现：Achieve()
            一定要在调用完成之后，记得free()释放。
 
        申请在常量区的空间不会被回收，指导程序结束才会被回收，可以地址不会变，直接可以传。
 
    e）指向函数的指针
        格式： 数据类型 (*指针名称)(函数参数)
        例：int (*pFunction)(int num1,int num2)
            表示声明了一个指向函数的指针，叫做pFunction，这个指针只能返回int类型，并且有两个整型的参数。
 
        指向函数的指针的初始化--取到符合指针条件的地址，函数的名称就代表函数的地址。
 
        例：
 
 
 
 
 5）无论指针是什么类型的，它占据的空间大小都为8个字节，那么为什么还要区分指针的类型呢？
     int num = 10;
     int* p1 = &num;
    p1指针变量中存储的是num变量的地址，也就是num变量的低字节地址，通过p1指针其实只能找到这个地址的一个字节。
    通过指针对实参进行赋值的时候，指针变量不清楚需要操作多少个字节空间，这个时候就是根据字节的数据类型来决定的。
    如果指针是int*类型的 那么就在指向的地址字节连续向高地址操作4个字节；
    如果指针是float*类型的 那么就在指向的地址字节连续向高地址操作4个字节；
    如果指针是double*类型的 那么就在指向的地址字节连续向高地址操作8个字节；
    如果指针是char*类型的 那么就在指向的地址字节连续向高地址操作1个字节。
 
 6）多级指针
     a. 声明一级指针：数据类型*    指针名;    一级指针只能存储普通变量的地址；
     b. 声明二级指针：数据类型**   指针名;    二级指针只能存储一级指针变量的地址；
     c. 声明三级指针：数据类型***  指针名;    三级指针只能存储二级指针变量的地址；
     d. 声明n级指针： 数据类型(n个*) 指针名;  N级指针只能存储（n-1）级指针变量的地址。
 
 7）指针可以与整数进行加减运算
    比如 指针 + n 相当于在这个指针地址的基础上 + n个sizeof(类型)指针变量类型的字节数
    应用：取数组的值
    例：UseThePointerToTakeTheValueOfTheArray();
 
 8）当我们声明一个函数，如果这个函数的参数是数组，建议直接声明一个指向数组地址的指针变量。
    int array[] = {10,20,30};
    定义函数的参数为：
    void test(int* array,int length);
 
 9）索引的本质
    a. 指针变量后面可以通过中括号[]下标来访问数据
    b.  int* p1;
        p1[n] 完全等价于 *(p1 + n)   例：PointerIndex();
 
     int array[] = {10,20,30};
     array[0] = 100; //等价于 *(array + 0)
     array[1] = 200; //等价于 *(array + 1)  本质也是通过指针赋值
 
 10）指针与指针之间只可以进行减法运算
    结果是一个long类型
    结果的意义： 表示两个指针指向的变量之间相差多少个单位变量。  两个地址差/普通类型的字节数
 
    注意：如果两个指针不是指向同一个数组，那么进行减法运算，可能会出现问题。
 
 
 11）内存中的五块区域 - 都是用来存数据的，主要目的是方便系统管理
    a. 栈 - 专门用来存储局部变量
    b. 堆 - 允许程序员手动申请空间使用，申请连续的指定字节的空间，例如5字节。
        在 <stdlib.h> 中，使用需要引入头文件
        1. 使用malloc函数
            格式：maclloc(size_t __size)
            返回值是：void * 代表没有类型的指针。具体用什么类型接，需要看想怎么操作这个数据。
            如果未初始化，会有垃圾值参数。
 
            在堆区申请的字节空间是从低地址向高地址分配，每一次申请的字节空间不一定挨在一起。
            申请字节空间不够的时候，会返回NULL，表示申请失败。
 
             int* p1 = malloc(20);
             if(p1)
             {
                //表示申请成功
                ...
 
                //申请的空间使用完毕，一定要记得释放。
                free(p1);
             }
 
        2.calloc函数
            格式：calloc(size_t __count,size_t __size);
            参数1：需要申请多少个单位  参数2：每一个单位的字节数
            例： int* p1 = calloc(4,sizeof(int)); 表示：申请4个int类型的指针
 
            与malloc相比的优势在于：calloc申请的字节，申请完毕后，系统自动初始化为0；
 
        3.realloc函数 -> 扩容
            当我们发现之前申请的堆区的字节空间不够用的时候，可以使用realloc进行扩容
            格式：realloc(void *__ptr, size_t __size)
            a。如果原来的空间后面还有剩余的空间，并且足够扩容，那么就直接往连续字节扩容在后面；
            b。如果原来的空间不够扩容，系统就会重新赋值一块足够字节内容的地址。将原来的数据拷贝过来，原来的空间被自动释放。
 
 
 
 
 
    c. BSS段 - 用来存储未初始化的全局变量和未初始化的静态变量
    d. 数据段/常量区 - 用来存储已经初始化的全局变量、静态变量和常量数据
    e. 代码段 - 用来存储程序的代码、指令
 
12）字符串的两个赋值方式的区别： - 在内存中的存储的结构不同
    int name1[] = "jack";
    int* name2 = "rose";
 
    a。当它们都是局部变量的时候，
    name1[]数组是申请在栈区，字符串的每个字符存储在这个数组的每一个元素中。
    name2是一个局部变量的指针，指针变量的声明在栈区，但是字符串数据以字符数组的形式存储在常量区，name2指针存储的是“rose”在常量区的地址
 
    b。当它们都是全局变量的时候，
    name1[]数组已经初始化，所以字符数组是存储在常量区的，字符串的每个字符存储在这个数组的每一个元素中。
    nam2是一个全局变量的指针，也已经初始化，所以也是存储在常量区，字符串也是以字符数组的形式存储在常量区。
 
    注意：a. 以字符数组存储的字符串数据，无论声明为全局变量还是局部变量，都可以通过使用索引下标的方式修改数组中的值。
            但是，以字符指针存储的字符串数组，无论声明为全局还是局部变量，都不能通过指针去修改指向的字符串数据。
        b. 当用字符指针存储一个字符串数据的时候，系统会先检查常量区中是否存在相同字符且都为字符指针的数据，
           如果有，直接将这个字符串的地址赋值给字符指针； 例：CharacterPointer();
           如果没有，则将这个字符串数据存储在常量区，一旦存储，就无法更改。
 
    建议以后使用字符指针来存储字符串数据，因为字符数组一旦初始化创建，长度就固定了。而字符指针存储字符串可以更改任意长度。
 
    面试题：
        “jknwnnfaksionewhbhbawhiudniuanwuaiocejiaiuwha” 求u出现的次数
 
 14）定义指针字符串数组，存储多个字符串
    char* name[] = {"jack","rose","Lily","MaoYeye"};
    这是一个一位数组，每个元素都为char指针，元素中存储的是字符串在常量区的地址。
 
    存储指针的数组，每一个元素的类型是一个指针，而每一个指针都是占据8个字节。即在求指针字符数组字节数为：
    sizeof(name)/8 保险起见，建议sizeof(name)/sizeof(name[0])
 
    相比二维字符数组name[][int]的优点：每个字符串的长度是任意的。
 
15）字符串排序
    例：StringSorting();
 
 16）fputs()函数
 
    使用格式：
    fputs(要输出的字符串,指定的流);
 
 a. 使用fputs函数，将字符串输出到标准输出流
    标准输出流：控制台，stdout; 例： StringToStdout();
 
 b. 将字符串存储到文件中  例：StingToFile();
     1.要先声明一个文件指针，指向磁盘上的文件
        fopen函数可以创建一个指向文件的指针
        fopen函数的两个参数：
        第一个参数：文件的路径，代表创建的指针就指向这个文件。
        第二个参数：操作这个文件的模式
            "w" -> write    代表你要向这个文件写入内容，如果文件不存在，系统会自动创建
            "r" -> read     代表你从这个文件中读取数据
            "a" -> append   代表你向这个文件追加数据
     2.使用fputs函数将字符串写入到指定的文件流中
        fputs(字符串,文件指针);
 
     3.写完之后，一定要记得使用fclose()函数，将这个文件关闭。
 
 17）fgets()函数
    作用：从指定的流中读取字符串
    特点：接收空格、安全。
    语法：fgets(要将字符串存入哪个数组中，最多接收多少个长度的字符串，指定流);
 
    1.从Stdin标准输入流接收数据 例：FgetsFromStdin();
    定义接收n个字符，
    如果传入的数据大于n-1，那么系统只会存储n-1个字符数据，并以"\0"结束。
    如果输入的字符数据小于n-1，那么就会将我们最后输入的换行符“\n”一并接收。
    解决方案：
    判断数组字符数据的最后一个是不是"\n"，如果是换成"\0"即可。
    标准的输入流：stdin；
 
    2.从文件中读取数据  例：FgetsFromFile();
    a.先创建一个文件指针
    b.建立存储读取到的字符串数据
    c.使用fgets()函数从指定的文件中读取
    d.查看数据情况
    e.关闭文件流fcolse();
 
 
 
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

//求数组中的最大值和最小值
int TheMaxNumberAndMinNumber(int* min);

//返回多个数据
void ReturnMultipeData(void);

//第二种方法求数组中的最大值和最小值
void MaximumAndMinimun(int array[],int length,int* max,int* min);

//第二种方法返回数据
void ReturnTheData(void);

//通过指针取数组的值
void UseThePointerToTakeTheValueOfTheArray(void);

//指针索引的本质
void PointerIndex(void);

//字符指针
void CharacterPointer(void);

//面试题
void InterviewQuestion(void);

//字符串排序
void StringSorting(void);

//将字符串输出到控制台
void StringToStdout(void);

//将字符串存储到文件流中
void StringToFile(void);

//将字符串从键盘上输入
void FgetsFromStdin(void);

//从文件中读取字符串
void FgetsFromFile(void);

//返回指针局部变量的情况
int* ReturnPointerLocalVariable(void);

//返回定义在堆区域的指针
int* ReturnStackVariable(void);

//两者返回指针的具体实现
void Achieve(void);

//指向函数的指针
void PointerToFunction(void);

int main(int argc, const char * argv[])
{
    
    
    PointerToFunction();
    
    
    return 0;
}


//指向函数的指针
void PointerToFunction(void)
{
    void (*pFunction)(void) = FgetsFromFile;//函数名就是函数的地址
    
    //通过指针间接的调用函数
    
    //第一种方法
    pFunction();
    
    //第二种方法
    (*pFunction)();
    
    
    
    
}


//两者返回指针的具体实现
void Achieve(void)
{
    //返回指针局部变量的情况
    int* p1 = ReturnPointerLocalVariable();
    for (int i = 0; i < 3; i++)
    {
        printf("%d\n",p1[i]); //10,垃圾值,垃圾值
    }
    
    
    //返回定义在堆区域的指针
    
    int* p2 = ReturnStackVariable();
    for (int i = 0; i < 3; i++)
    {
        printf("%d\n",p2[i]);
    }
    free(p2);
    
}

//返回定义在堆区域的指针
int* ReturnStackVariable(void)
{
    int* arr = calloc(3, sizeof(int));
    *arr = 10;
    *(arr + 1) = 20;
    *(arr + 2) = 30;
    
    return arr;
    
}

//返回指针局部变量的情况
int* ReturnPointerLocalVariable(void)
{
    int arr[] = {10,20,30};
    
    return arr;
}


//从文件中读取字符串
void FgetsFromFile(void)
{

    FILE* pFile = fopen("/Users/Admin/Desktop/Learning-C/Pointer/file.text", "r");
    char content[100];
    fgets(content, 100, pFile);
    printf("%s",content);
    fclose(pFile);
    
}


//将字符串从键盘上输入
void FgetsFromStdin(void)
{
    char greet[10];
    printf("请输入:\n");
    fgets(greet, 10, stdin);
    unsigned long length = strlen(greet);
    if (greet[length - 1] == '\n')
    {
        greet[length - 1] = '\0';
    }
    
    printf("你输入的是：%s\n",greet);
   
    
}

//将字符串存储到文件流中
void StringToFile(void)
{
    
    FILE* pFile = fopen("/Users/Admin/Desktop/Learning-C/Pointer/file.text", "w");
    char* greet = "猫爷爷的酱醋茶，加油啊，坚持住，未来会更好!\n";
    fputs(greet, pFile);
    fclose(pFile);
    
}

//将字符串输出到控制台
void StringToStdout(void)
{
    char* greet = "猫爷爷的酱醋茶，加油啊，坚持住!\n";
    fputs(greet, stdout);
}

//字符串排序 - 冒泡排序
void StringSorting(void)
{
    char *countries[] =
    {
        "Nepal",
        "Cambodia",
        "Afghanistan",
        "China",
        "Singapore",
        "Bangladesh",
        "India",
        "Maldives",
        "South Korea",
        "Bhutan",
        "Japan",
        "Sikkim",
        "Sri Lanka",
        "Burma",
        "North Korea",
        "Laos",
        "Malaysia",
        "Indonesia",
        "Turkey",
        "Mongolia",
        "Pakistan",
        "Philippines",
        "Vietnam",
        "Palestine"
    };
    
    unsigned long length = sizeof(countries)/sizeof(countries[0]);
    
    for (int i = 0; i < length - 1; i++)
    {
        for (int j = 0; j < length - 1 - i; j++)
        {
            int res = strcmp(countries[j],countries[j+1]);
            if (res > 0)
            {
                char* temp = countries[j];
                countries[j] = countries[j+1];
                countries[j+1] = temp;
            }
        }
    }
    
    for (int i = 0; i < length; i++)
    {
        printf("%s\n",countries[i]);
    }
    
    
}

//面试题
void InterviewQuestion(void)
{
    
    char* name = "urjknwnnfaksionewhbhbawhiudniuanwuaiocejiaiuwha";
    int frequency = 0;
    int i = 0;
    while (*(name + i) != '\0')
    {
        if (*(name + i) == 'u')
        {
            frequency += 1;
        }
       
        i += 1;
    }
    printf("这个字符串中u出现的次数为：%d\n",frequency);
    
    //第二种写法
    i = 0;
    frequency = 0;
    while (name[i] != '\0')
    {
        if (*(name + i) == 'u')
        {
            frequency += 1;
        }
        
        i += 1;
    }
    printf("这个字符串中u出现的次数为：%d\n",frequency);
}

//字符指针
void CharacterPointer(void)
{
    
    char* name1 = "jack";
    char* name2 = "jack";
    char* name3 = "jack";
    
    printf("name1 = %p,\nname2 = %p,\nname3 = %p\n",name1,name2,name3);//输出的值相同
    char name[] = "rose";
    name3 = "rose";//并不是把name3的“jack”改为“rose”，这是重新创建一个“rose”，并把它的地址赋值给name3
    //因为name[]不是指针字符串，所以即使字符相同，name3也不能指向name[]的地址
    printf("name3 = %p,里面的值为%s\n",name3,name3);
    printf("name的地址为%p\n",name);
    
}

//指针索引的本质
void PointerIndex(void)
{
    
    int num1 = 0;
    int num2 = 0;
    int* p1 = &num2;
    p1[0] = 1000; //相当于 *(p1 + 0) 即为num2
    p1[1] = 2000; //相当于 *(p1 + 1) 即为num1
    printf("num1 = %d\n",num1);
    printf("num2 = %d\n",num2);
    
}

//通过指针取数组的值
void UseThePointerToTakeTheValueOfTheArray()
{
    int array[] = {23,35,2,65,56,65,98,34,77,10,56,34,23,65,5};
    int length = sizeof(array)/sizeof(array[0]);
    
    //第一种方式
    int* p1 = array;
    for (int i = 0; i < length; i++)
    {
        printf("%d  ",*(p1 + i));
    }
    printf("\n");
    
    //第二种方式
    for (int i = 0; i < length; i++) {
        printf("%d  ",*(array + i));
    }
     printf("\n");
    
    
    //第三种方法
    for (int i = 0; i < length; i++)
    {
        printf("%d  ",*(p1++));
    }
    p1 = array;
    printf("\n");
    for (int i = 0; i < length; i++)
    {
        printf("%d  ",*(p1++));
    }
     printf("\n");
}

//第二种方法返回数据
void ReturnTheData(void)
{
    int array[] = {23,35,2,65,56,65,98,34,77,10,56,34,23,65,5};
    int length  = sizeof(array)/sizeof(array[0]);
    int max = 0;
    int min = 0;
    MaximumAndMinimun(array, length, &max, &min);
    printf("这个数组的最大值为%d\n",max);
    printf("这个数组的最小值为%d\n",min);
    
}


//第二种方法求数组中的最大值和最小值
void MaximumAndMinimun(int array[],int length,int* max,int* min)
{
    
    *max = INT32_MIN;
    *min = INT32_MAX;
    for (int i = 0; i < length; i++)
    {
        if (array[i] > *max)
        {
            *max  = array[i];
        }
        
        if (array[i] < *min)
        {
            *min = array[i];
        }
    }
}


//返回多个数据
void ReturnMultipeData(void)
{
    int min = 0;
    int max = TheMaxNumberAndMinNumber(&min);
    
    printf("这个数组的最大值为：%d\n",max);
    printf("这个数组的最小值为：%d\n",min);
}

//求数组中的最大值和最小值
int TheMaxNumberAndMinNumber(int* min )
{
    
    int array[] = {23,35,2,65,56,65,98,34,77,10,56,34,23,65,5};
    int length  = sizeof(array)/sizeof(array[0]);
    int max = INT32_MIN;
    *min = INT32_MAX;
    for (int i = 0; i < length; i++)
    {
        if (array[i] > max)
        {
            max  = array[i];
        }
        
        if (array[i] < *min)
        {
            *min = array[i];
        }
    }
    
    return max;
    
    
    
}
